package org.alicuu.ioc.reader;

import org.alicuu.toolkit.KumamonTooKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

/**
 * Created by IntelliJ IDEA.
 * User: Danny
 * Date: 16/07/04
 * Time: 21:36
 */
public class AbstractClassReader implements ClassReader {
    private static final Logger log = LoggerFactory.getLogger(AbstractClassReader.class);

    public Set<ClassInfo> getClass(String packageName, boolean recursive) {
        return getClassByAnnotation(packageName, null, null, recursive);
    }

    public Set<ClassInfo> getClass(String packageNamw, Class<?> parent, boolean recursive) {
        return getClassByAnnotation(packageNamw, parent, null, recursive);
    }

    @Override
    public Set<ClassInfo> getClassByAnnotation(String packageName, Class<? extends Annotation> annotation, boolean recursive) {
        return this.getClassByAnnotation(packageName, null, annotation, recursive);
    }


    public Set<ClassInfo> getClassByAnnotation(String packageName, Class<?> parent, Class<? extends Annotation> annotation, boolean recursive) {
        Set<ClassInfo> classes = new HashSet<>();
        String packageDirName = packageName.replace(".", "/");
        Enumeration<URL> dirs;
        try {
            dirs = this.getClass().getClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String filePath = new URI(url.getFile()).getPath();
                Set<ClassInfo> subClasses = findClassByPackage(packageName, filePath, parent, annotation, recursive, classes);
                if (KumamonTooKit.isNotEmpty(subClasses))
                    classes.addAll(subClasses);
            }
        } catch (IOException | ClassNotFoundException | URISyntaxException e) {
            log.error(e.getMessage(), e);
        }

        return classes;
    }


    private Set<ClassInfo> findClassByPackage(final String packageName, final String packagePath,
                                              final Class<?> parent, final Class<? extends Annotation> annotation,
                                              final boolean recursive, Set<ClassInfo> classes) throws ClassNotFoundException {

        File dir = new File(packagePath);
        if ((!dir.exists()) || (!dir.isDirectory()))
            log.warn("The package [{}] not found.", packageName);

        File[] files = getClassFiles(dir, recursive);

        if (null != files && files.length > 0) {
            for (File file : files) {
                if (file.isDirectory())
                    findClassByPackage(packageName + "." + file.getName(), file.getAbsolutePath(), parent, annotation, recursive, classes);
                else {
                    String className = file.getName().substring(0, file.getName().length() - 6);
                    Class<?> clazz = Class.forName(packageName + "." + className);
                    if (null != parent && null != annotation) {
                        if (null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent) && null != clazz.getAnnotation(annotation))
                            classes.add(new ClassInfo(clazz));
                        continue;
                    }
                    if (null != parent) {
                        if (null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent))
                            classes.add(new ClassInfo(clazz));
                        else {
                            if (null != clazz.getInterfaces() && clazz.getInterfaces().length > 0 && clazz.getInterfaces()[0].equals(parent))
                                classes.add(new ClassInfo(clazz));
                        }
                        continue;
                    }
                    if (null != annotation) {
                        if (null != clazz.getAnnotation(annotation))
                            classes.add(new ClassInfo(clazz));
                        continue;
                    }
                    classes.add(new ClassInfo(clazz));
                }
            }
        }

        return classes;
    }

    private File[] getClassFiles(File dir, final boolean recursive) {
        return dir.listFiles(new FilenameFilter() {
            public boolean accept(File file, String name) {
                if (recursive) if (file.isDirectory()) return true;
                return file.getName().endsWith(".class");
            }
        });
    }


}
